using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

namespace MicroLiquidCrystal
{
    public class I2CLcdTransferProvider : ILcdTransferProvider, IDisposable
    {
        private bool _fourBitMode;
        // I2C Provider only one instance is allowed.
        // for further implementation of I2C devices a factory will be required
        private I2CDevice.Configuration _i2cConfig;
        private I2CDevice _i2cDevice;

        private const byte MODE_DATA = 1;
        private const byte ENABLE = 4;
        private const byte LIGHT = 8;
        private const byte DB4 = 16;
        private const byte DB5 = 32;
        private const byte DB6 = 64;
        private const byte DB7 = 128;
        private const byte MODE_DATA_INV = 254;
        private const byte ENABLE_INV = 251;
        private const byte LIGHT_INV = 247;

        public I2CLcdTransferProvider()
        {
            _fourBitMode = true;
            _i2cConfig = new I2CDevice.Configuration(0x27, 100);
            _i2cDevice = new I2CDevice(_i2cConfig);
        }

        /// <summary>
        /// Write either command or Data, I2CDevice is 4 bit mode only !
        /// </summary>
        /// <param name="Value">Value to write</param>
        /// <param name="Mode">Mode for RS (register select) pin, true - Data is sent , false - Command is sent.</param>
        /// <param name="Backlight">Backlight state.</param>
        public void Send(byte Data, bool Mode, bool Backlight)
        {
            // I2cDevice is 4 bit Mode only
            byte[] writeBuffer = new byte[1];
            I2CDevice.I2CTransaction[] writeTrans;
            int write = 0;
            writeTrans = new I2CDevice.I2CTransaction[]
            {
                I2CDevice.CreateWriteTransaction(writeBuffer)
            };

            // set 4 lower bits to 0, to clean them for command bits required
            writeBuffer[0] = (byte)(Data & (byte)240);
            PrepareValue(writeBuffer, Mode, Backlight);
            write = _i2cDevice.Execute(writeTrans, 1000);
            PulseEnable(writeTrans);

            // shit 4 lower bits to upper area, to clean them for command bits required
            writeBuffer[0] = (byte)(Data << 4);
            PrepareValue(writeBuffer, Mode, Backlight);
            write = _i2cDevice.Execute(writeTrans, 1000);
            PulseEnable(writeTrans);
        }

        /// <summary>
        /// Creates Pulse on ENABLE pin of LCD
        /// </summary>
        /// <param name="WriteTransaction">WriteTransaction to execute pulse on</param>
        private void PulseEnable(I2CDevice.I2CTransaction[] WriteTransaction)
        {
            int write = 0;

            // & set Enable false
            WriteTransaction[0].Buffer[0] &= ENABLE_INV;
            write = _i2cDevice.Execute(WriteTransaction, 1000);
            // OR set ENABLE true
            // enable pulse must be >450ns, add sleep if required
            WriteTransaction[0].Buffer[0] |= ENABLE;
            write = _i2cDevice.Execute(WriteTransaction, 1000);
            // & set Enable false
            // commands need > 37us to settle, add sleep if required
            WriteTransaction[0].Buffer[0] &= ENABLE_INV;
            write = _i2cDevice.Execute(WriteTransaction, 1000);
        }

        /// <summary>
        /// Prepares WriteBuffer with command bits required
        /// </summary>
        /// <param name="WriteBuffer">WriteBuffer to to be prepared, in out parameter</param>
        /// <param name="Mode">Mode for RS (register select) pin, true - Data is sent , false - Command is sent.</param>
        /// <param name="Backlight">Backlight state.</param>
        private void PrepareValue(byte[] WriteBuffer, bool Mode, bool Backlight)
        {
            if (Backlight)
            {
                // OR sets bit
                WriteBuffer[0] |= LIGHT;
            }
            else
            {
                // & deletes bit
                WriteBuffer[0] &= LIGHT_INV;
            }
            if (Mode)
            {
                // OR sets bit
                WriteBuffer[0] |= MODE_DATA;
            }
            else
            {
                // & deletes bit
                WriteBuffer[0] &= MODE_DATA_INV;
            }
        }

        /// <summary>
        /// Specify if the provider works in 4-bit Mode; I2C DEvice provides 4-bit Mode only !
        /// </summary>
        public bool FourBitMode
        {
            get { return _fourBitMode; }
        }


        public void Dispose()
        {
            _i2cDevice.Dispose();
        }

    }
}
